﻿#ifndef EDGEEXTRACT_H
#define EDGEEXTRACT_H

#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"

/**
 * @brief The EdgeExtract class 亚像素级别的边缘提取算法。参考了CSDN 博客文章 https://blog.csdn.net/yx123919804/article/details/103123071
 *                              本代码主要用于学习目的。尽可能保证代码清晰易读。因此没有做太多优化。
 *                              如需提高速度，可以进一步优化，比如采用 OPENMP 实现多线程，利用对称性，将 8 个方向的梯度计算精简为 4 个方向等。
 * @author 李渊, email: 18069211@qq.com
 * @version v0.9
 * @date 2022/10/28
 * 本代码采用 GPLv3 开源协议
 */

namespace EdgeExtract {
enum SOBEL_DIR {SOBEL_DIR_0, SOBEL_DIR_45, SOBEL_DIR_90, SOBEL_DIR_135, SOBEL_DIR_180, SOBEL_DIR_225, SOBEL_DIR_270, SOBEL_DIR_315};
/**
 * @brief color2Gray 彩色图像转换为灰度图像
 * @param inImage [in] 彩色图像，也可以是灰度图像。如果是灰度图像则不做变换。
 * @param gray    [out] 输出的灰度图像。如果输入图像就是灰度图像，则输出的图像与输入图像共享内存。
 * @return true 表示转换成功，false 表示转换失败。失败通常是由于输入图像为空。
 */
bool color2Gray(const cv::Mat &inImage, cv::Mat &gray);

/**
 * @brief gradient 利用 SOBEL 算子计算梯度
 * @param inImage 输入图像，可以是彩色图像或者灰度图像。如果是彩色图像，会在内部转换为灰度图像。
 * @param gradImage 计算出的梯度图像，CV_16SC1 类型
 * @param dir 梯度方向，只支持 8 个方向
 * @return 表示成功，false 则表示失败。失败通常是由于输入图像为空。
 */
bool gradient(const cv::Mat & inImage, cv::Mat & gradImage, enum SOBEL_DIR dir); // 计算单个方向的梯度

/**
 * @brief dirEdge
 * @param inImage 输入图像，可以是彩色图像或者灰度图像。如果是彩色图像，会在内部转换为灰度图像。
 * @param edge 计算得出的边缘点图像。在梯度方向上做了非极大值抑制。边缘点取值 255，非边缘点取值 0。类型为 CV_8UC1
 * @param dir 梯度方向，只支持 8 个方向
 * @param threshold 边缘提取的阈值，只有大于 threshold 的边缘才会被提取
 * @return 表示成功，false 则表示失败。失败通常是由于输入图像为空。
 */
bool dirEdge(const cv::Mat inImage, cv::Mat & edge8U, SOBEL_DIR dir, int threshold); // 朝 dir 方向求梯度，并提取边缘

/**
 * @brief amplitudeAndAngle 计算一幅图像的边缘的强度和角度
 * @param inImage 输入图像，可以是彩色图像或者灰度图像。如果是彩色图像，会在内部转换为灰度图像。
 * @param amp 得到图像中各点的最强梯度。（8个方向中最强的那个梯度） CV_16SC1 类型。
 * @param angle 梯度的方向。只能取 8 个值中之一。 CV_16SC1 类型。
 * @return true 表示成功，false 则表示失败。失败通常是由于输入图像为空。
 */
bool amplitudeAndAngle(const cv::Mat & inImage, cv::Mat & amp16S, cv::Mat & angle16S); //计算梯度的幅度和角度

/**
 * @brief edge 利用 SOBEL 算子进行边缘提取，获取像素级精度的边缘
 * @param inImage 输入图像，可以是彩色图像或者灰度图像。如果是彩色图像，会在内部转换为灰度图像。
 * @param edge 计算得出的边缘点图像。在梯度方向上做了非极大值抑制。边缘点取值 255，非边缘点取值 0。类型为 CV_8UC1
 * @param threshold 边缘提取的阈值，只有大于 threshold 的边缘才会被提取
 * @return 表示成功，false 则表示失败。失败通常是由于输入图像为空。
 */
bool edge(const cv::Mat & inImage, cv::Mat & edge8U, int threshold = 0);// 获取像素级精度的边缘
/**
 * @brief subPixelEdge 利用 SOBEL 算子进行边缘提取，获取亚像素级的边缘
 * @param inImage 输入图像，可以是彩色图像或者灰度图像。如果是彩色图像，会在内部转换为灰度图像。
 * @param edge 计算得出的边缘点图像。在梯度方向上做了非极大值抑制。边缘点取值 255，非边缘点取值 0。类型为 CV_8UC1
 * @param points 亚像素精度的边缘点的坐标。
 * @param threshold 边缘提取的阈值，只有大于 threshold 的边缘才会被提取
 * @return 表示成功，false 则表示失败。失败通常是由于输入图像为空。
 */
bool subPixelEdge(const cv::Mat & inImage, cv::Mat & edge8U, std::vector<cv::Point2f> & points, int threshold);
/**
 * @brief subPixelEdge 利用 SOBEL 算子进行边缘提取，获取亚像素级的边缘
 * @param inImage 输入图像，可以是彩色图像或者灰度图像。如果是彩色图像，会在内部转换为灰度图像。
 * @param edge 计算得出的边缘点图像。在梯度方向上做了非极大值抑制。边缘点取值 255，非边缘点取值 0。类型为 CV_8UC1
 * @param coordinate 存储亚像素级的边缘点的坐标。CV_32FC2 类型，第一个通道存储 x 方向坐标，第二个通道存储 y 方向坐标。
 * @param threshold 边缘提取的阈值，只有大于 threshold 的边缘才会被提取
 * @return 表示成功，false 则表示失败。失败通常是由于输入图像为空。
 */
bool subPixelEdge(const cv::Mat & inImage, cv::Mat & edge8U, cv::Mat & coordinate32FC2, int threshold);

void gradient(const cv::Mat & inImage, cv::Mat gradients16S[8]); // 利用 SOBEL 算子计算 8 个方向的梯度
void gradients2AmpAngle(cv::Mat gradients[8], cv::Mat & ampAndAngle); //通过 8 个梯度计算幅度和角度
void gradients2AmpAngle(cv::Mat gradients[8], cv::Mat & amp, cv::Mat & angle16S); //通过 8 个梯度计算幅度和角度
void ampAngle2Edge(const cv::Mat & amp, const cv::Mat & angle, cv::Mat & edge, int threshold = 0); // 获取像素级精度的边缘
void ampAngle2SubPixelEdge(const cv::Mat & amp16S, const cv::Mat & angle16S, cv::Mat & edge8U, cv::Mat & coordinate32F, int threshold = 0);
std::vector<cv::Point2f> ampAngle2SubPixelEdge(const cv::Mat & amp, const cv::Mat & angle, cv::Mat & edge, int threshold = 0);
}

#endif // EDGEEXTRACT_H
